package jp.co.nes.awssupport.common.utility;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * データベースコネクションに関するユーティリティです。<br>
 * コネクションは、複数個のコネクションを管理します。
 */
public class DbConnUtil {

    /** ロガー */
    private static Logger logger = LoggerFactory.getLogger(DbConnUtil.class);

    /**
     * コネクションの種類を表す列挙型です。
     */
    private static enum ConnType {
        NORMAL, LARGE_READ
    }

    /** コネクションマップ */
    private static Map<ConnType, Map<Integer, Connection>> connMap = new HashMap<>();

    /**
     * デフォルトコンストラクタです。<br>
     * 本クラスはインスタンスを生成しません。
     */
    private DbConnUtil() {
    }

    /**
     * コネクションを返却します。
     *
     * @return コネクション
     */
    public static Connection getConn() {
        return getConn(ConnType.NORMAL, 0);
    }

    /**
     * コネクションをコミットします。
     */
    public static void commit() {
        commit(ConnType.NORMAL, 0);
    }

    /**
     * コネクションをロールバックします。
     */
    public static void rollback() {
        rollback(ConnType.NORMAL, 0);
    }

    /**
     * 大量データ読み込み用Readerクラスで使用するためのコネクションを返却します。
     *
     * @return コネクション
     */
    public static Connection getConnForLargeRead() {
        return getConn(ConnType.LARGE_READ, 0);
    }

    /**
     * 大量データ読み込み用Readerクラスで使用したコネクションをコミットします。
     */
    public static void commitForLargeRead() {
        commit(ConnType.LARGE_READ, 0);
    }

    /**
     * 大量データ読み込み用Readerクラスで使用したコネクションをロールバックします。
     */
    public static void rollbackForLargeRead() {
        rollback(ConnType.LARGE_READ, 0);
    }

    /**
     * 全てのコネクションをコミットします。
     */
    public static void commitAll() {
        Set<ConnType> keySet = connMap.keySet();
        for (ConnType connType : keySet) {
            int size = connMap.get(connType).size();
            for (int i = 0; i < size; i++) {
                commit(connType, i);
            }
        }
    }

    /**
     * 全てのコネクションをロールバックします。
     */
    public static void rollbackAll() {
        Set<ConnType> keySet = connMap.keySet();
        for (ConnType connType : keySet) {
            int size = connMap.get(connType).size();
            for (int i = 0; i < size; i++) {
                rollback(connType, i);
            }
        }
    }

    /**
     * 全てのコネクションをクローズします。
     */
    public static void closeAll() {
        Set<ConnType> keySet = connMap.keySet();
        for (ConnType connType : keySet) {
            int size = connMap.get(connType).size();
            for (int i = 0; i < size; i++) {
                close(connType, i);
            }
        }
        connMap.clear();
    }

    /**
     * 引数で指定された種類のコネクションを返却します。
     *
     * @param connType コネクションの種類
     * @param index コネクションのインデックス
     * @return コネクション
     */
    private static Connection getConn(ConnType connType, int index) {
        Map<Integer, Connection> map = connMap.get(connType);
        if (map == null) {
            map = new HashMap<>();
            connMap.put(connType, map);
        }
        Connection connection = map.get(index);
        if (connection == null) {
            connection = connect();
            map.put(index, connection);
        }
        return connection;
    }

    /**
     * コネクションを取得します。
     */
    private static Connection connect() {
        String driver = AppInfoUtil.getVal("Rds", "JdbcDriver");
        String url = AppInfoUtil.getVal("Rds", "JdbcUrl");
        String user = AppInfoUtil.getVal("Rds", "JdbcUser");
        String password = null;
        String encryptPassword = AppInfoUtil.getVal("Rds","EncryptJdbcPassword");
        if (encryptPassword == null) {
            password = AppInfoUtil.getVal("Rds","JdbcPassword");
        } else {
            AwsKmsHelper helper = new AwsKmsHelper();
            password = helper.decrypt(encryptPassword);
        }
        try {
            Class.forName(driver);
            Connection connection = DriverManager.getConnection(url, user, password);
            connection.setAutoCommit(false);
            return connection;
        } catch (ClassNotFoundException | SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 引数で指定された種類のコネクションをクローズします。
     *
     * @param connType コネクションの種類
     * @param index コネクションのインデックス
     */
    private static void close(ConnType connType, int index) {
        Connection connection = getConn(connType, index);
        try {
            if (connection != null && !connection.isClosed()) {
                connection.close();
            }
        } catch (SQLException e) {
            // ログを出力して、処理を続行する。
            logger.warn(e.getMessage(), e);
        }
    }

    /**
     * 引数で指定された種類のコネクションをコミットします。
     *
     * @param connType コネクションの種類
     * @param index コネクションのインデックス
     */
    private static void commit(ConnType connType, int index) {
        try {
            Connection connection = getConn(connType, index);
            if (connection == null) {
                logger.debug("connection(type=NORMAL) is null");
            } else {
                connection.commit();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 引数で指定された種類のコネクションをロールバックします。
     *
     * @param connType コネクションの種類
     * @param index コネクションのインデックス
     */
    private static void rollback(ConnType connType, int index) {
        try {
            Connection connection = getConn(connType, index);
            if (connection == null) {
                logger.debug("connection(type=NORMAL) is null");
            } else {
                connection.rollback();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}
